Skip to main content
This forum is closed to new posts and responses. Individual names altered for privacy purposes. The information contained in this website is provided for informational purposes only and should not be construed as a forum for customer support requests. Any customer support requests should be directed to the official HCL customer support channels below:

HCL Software Customer Support Portal for U.S. Federal Government clients
HCL Software Customer Support Portal

Notes/Domino 8 Forum

Notes/Domino 8 Forum

Previous Next

3.2 Developing Composite Applications: Expeditor Components

How to Write an Eclipse Component for Notes
Jo Grant, Craig Wolpert

Intro
Composite applications are a key element in a service-oriented architecture (SOA) and contextual collaboration strategy. They support business flexibility for companies and organizations who wish to rapidly respond to changing demands in today's competitive markets. Composite applications are aggregations of multiple components that are loosely coupled to support inter-component communication. Changes in data values in one component view can be broadcast through properties. These are then wired to actions in other component views to receive the change notification and react to it. Components can be reused in multiple composite applications. The ability to combine multiple technologies into a single application provides significant business value. It enables companies to protect and extend their existing assets with increasing degrees of flexibility, and to respond quickly and cost effectively to their emerging business requirements, with applications that are significantly easier to create than multiple application development environments.
The IBM(R) WebSphere(R) Portal composition application model is provided in a Notes-style approach. IBM(R) Lotus(R) Domino Designer(R) has been extended to leverage the property broker experience, as well as to provide a more intuitive user environment. Notes applications can be surfaced as one or more Notes component views in a composite application. Hannover also supports the inclusion of Eclipse components and a composite application may have any combination of Notes components and Eclipse components. These may be just be presented together in the same UI for on the glass integration or if extended to use the Property Broker will fully be able to interoperate with each other. You can define composite applications using the Composite Application Editor or the WebSphere Portal Application Template Editor.
Chapter five of the Composite Application Toolkit User's Guide discusses how to build Eclipse components. It gives a reference to the APIs used and several examples. There is also a tutorial that walks you through the creation of a simple Composite Application based on Notes components. There is also material and examples in the Expeditor Toolkit. For more information on this toolkit read Developing Applications for Lotus Expeditor at http://publib.boulder.ibm.com/infocenter/ledoc/v6r11/index.jsp?topic=/com.ibm.rcp.tools.doc.appdev/welcome.html. This article builds on that information and introduces some helper classes to help you quickly build and deploy feature rich reusable Eclipse based components. The components we discuss are usable in both Lotus Expeditor and Lotus Notes, but for simplicity the examples we give will be in reference to Notes.

Architecture
A component view is more than just a piece of UI. It is a programmatic unit that communicates and participates with the outside world. Through this communication capability, several component views are wired together to form a composite application. We are going to look at two features of the Expeditor and Notes platform that allow for this.
Topology Handler This feature allows static values to be set at run-time for a component view. In Notes using the Composite Application Editor (CAE) you can access properties for a component view through the Advanced Component Properties dialog. This allows for assembly time configuration of a component view.


Property Broker This feature allows component view-to-component view connections to be created. A "property" is a source value that a component view can set to indicate a change of value. An "action" is a destination value that a component view can indicate that it consumes.

Wiring "Wires" can be established at assembly time to connect properties output by components views to actions in components views that process changes to those properties. In Notes, use the Composite Application Editor to "wire" components.



Data Model Both of these features involve getting or setting the value of data elements in your component view. The conventional structure for storing collections of data elements in Java is a Java Bean. This, conveniently, also has a well defined mechanism for propagating changes that we will make use of.
There are a number of helper classes that we introduce over the course of this example. Although not required for the simple component view we are making, they can be re-used across components and are of great value when developing more complicated components.

Overall Strategy
An Eclipse component view is based on a View part. Coupled with this is a data model. Each instance of the component view has it's own instance of a data model. All the rest is coupling the pieces together. Your data model is coupled with your view to represent to the user the data, and to allow the user to interact and change the data. On the back end the data model is coupled with the property broker to broadcast changes to values in the data model, and to listen for changes imposed from other component views. Lastly it is coupled with the topology handler in order to read initial instance values into the data model.


Design
For this component view, we are going to create a "Tag Cloud" component view. We have a SWT control (See TagCloud.java) that we want to expose. We might use this to display the categories in a discussion database.



The first important decision to make is what data elements we are going to expose. For simplicity, we are going to choose two: PrimaryData, as HashMap of key-value pairs that drives the display model, and FocusedEntity which represents the current tag under focus. For example, the above screen shot shows an integrated application with a Discussion Database Notes Component View, and a Tag Cloud. Here information about all of the categories in the database are being weighted by the number of articles and sent for display to the tag cloud. Additionally categories may be selected in the tag cloud and that focus passed back to the Discussion Database component view. This uses view filtering to show only articles in that category.
For this release of Expeditor and Notes the only base property values allowed are Strings. A HashMap is a complex type though. The technique for dealing with this is to come up with a form of "serialization" that represents the complex type in a simple string. Even though only base types of Strings are supported, we can add typing data to distinguish semantically types of strings. We'll do that to ensure that only values of TagCloudDataString are passed in for the tag cloud. Since the FocusedEntity is just a key, we'll use the generic StringType.

Creating the Data Model
The data model we're going to use is a simple Java bean. To aid this we introduce our first helper class: PCSBean.java. This is a very simple class that uses the java.beans.PropertyChangeSupport class to create APIs to support the propagation of property changes.
Now we can create our bean as a sub class of this. In TagCloudViewBean.java you can see where we've done this, created the two data members, PrimaryData and FocusedEntity, and added setters and getters for them. Lastly we extend the setters to call into the functions of the base class to propagate notification of property changes.

public class TagCloudViewBean extends PCSBean {
private String mPrimaryData;
private String mFocusedEntity;

public TagCloudViewBean() {
mPrimaryData = "";
}
public String getPrimaryData() {
return mPrimaryData;
}
public void setPrimaryData(String PrimaryData) {
queuePropertyChange("primaryData", mPrimaryData, PrimaryData);
mPrimaryData = PrimaryData;
firePropertyChange();
}
public String getFocusedEntity() {
return mFocusedEntity;
}
public void setFocusedEntity(String focusedEntity) {
queuePropertyChange("focusedEntity", mFocusedEntity, focusedEntity);
mFocusedEntity = focusedEntity;
firePropertyChange();
}
}

The last part of the data model is to create the WSDL file that describes it to the PropertyBroker. This is contained in TagCloudView.wsdl. This file can be used as a template for creating WSDL files for your own components. For each property you have in your data model you need to add to the WSDL in a number of areas.
<types>
Determine the type of your property. In the implementation they are all going to be Strings. However we can add a semantic type to it in the WSDL file. These must be consistent from component to component. You can only create wires from properties to actions that are of the same semantic type. For the PrimaryData property we've decided to call the format TagCloudDataType. We declare it within the <types> section like this:
<types>
<xsd:schema targetNamespace="http://www.ibm.com/wps/c2a">
<xsd:simpleType name="TagCloudDataType">
<xsd:restriction base="xsd:string"/>
</xsd:simpleType>
</xsd:schema>
</types>
For other types, duplicate the <xsd:simpleType> tag group and rename "TagCloudDataType" with the desired type.
[Note: in addition to having the same semantic type, properties must also lie in the same namespace. Be sure to be consistent with your use of namespaces within your WSDL files across multiple components.]
<message>
Messages are collections of parameters for use as profiles in determining the input and output values for a function call. We need to have one for each getter and one for each setter. In our example we need:
<message name="getPrimaryDataResponse">
<part name="getPrimaryDataValue" type="tns:TagCloudDataType"/>
</message>
<message name="setPrimaryDataRequest">
<part name="PrimaryDataValue" type="tns:TagCloudDataType"/>
</message>
For other properties, add these same two messages again. Change "PrimaryData" to the base name of your property and "TagCloudDataType" to the type for your property.
<portType>
The port type links an operation (defined below) with the messages (defined above) for its input and output parameters. Since we are dealing with simple accessor methods for beans, we have one portType for the setter which contains a single input message, and one for the getter that contains a single output message.
<portType name="TagCloudView_Service">
<operation name="getPrimaryData">
<output message="tns:getPrimaryDataResponse"/>
</operation>
<operation name="setPrimaryData">
<input message="tns:setPrimaryDataRequest"/>
</operation>
</portType>
For other properties, duplicate the two <operation> tags. Change "PrimaryData" to the base name of your property.
<operation>
Lastly we create our operations in the <binding> group. These fully define the profile and refer to the previously defined constructs. We also declare the captions and descriptions used for these properties. This is what is used for display in the Composite Application Editor. (Note, you can declare a .properties file along with your .wsdl file using the conventional naming scheme for localization. Prefixing the value in your caption or description with % indicates that the Composite Application Editor should look up the value in the appropriate property file for the locale.)
<binding name="TagCloudViewBinding" type="tns:TagCloudView_Service">
<portlet:binding/>
<operation name="getPrimaryData">
<portlet:action name="getPrimaryData" caption="PrimaryData" description="data to drive weighting of tag cloud"/>
<output>
<portlet:param name="getPrimaryData" partname="getPrimaryDataValue" caption="PrimaryData" description="data to drive weighting of tag cloud"/>
</output>
</operation>
<operation name="setPrimaryData">
<portlet:action name="setPrimaryData" caption="PrimaryData" description="data to drive weighting of tag cloud"/>
<input>
<portlet:param name="setPrimaryData" partname="PrimaryDataValue" caption="PrimaryData" description="data to drive weighting of tag cloud"/>
</input>
</operation>
</binding>

For other properties, duplicate the two <operation> tags. Change "PrimaryData" to the base name of your property and add in appropriate descriptions.
Note: in this example we have exposed both the set and get access functions for our data type. For something like the FocusedEntity, it makes sense to have both. An outside component view can tell this component view where it wants the focus to be. And this component view, if the user clicks in the right area, can broadcast to other component views what the new selection is. While it is clear that we would want to be able to set the PrimaryData on this component view, there is not as clear a use case for letting other component views get it. However since assembly takes place after component creation, and possibly by someone else in a completely different application, it is worth considering exposing such things even if there is no immediate use seen for them. It is better to build in flexibility up front.
Now that we have created and defined our data model, we need to hook it up, programmatically, to the property broker mechanism.

Helper Class For Properties
We introduce the PBBroadcast.java helper class for managing the broadcasting of property changes from our data model to the property broker. When constructed we pass it in the view it is associated with, and the data model bean. In the constructor it attaches itself to the bean as a listener. When a value in the bean changes, the propertyChange() method is called. To broadcast the changed value we need to assemble a collection of Property-Value pairs and publish them to the broker. In this simple case we are only dealing with single property changes. In a more specific implementation you have the option of publishing several property changes at once.
public void propertyChange(PropertyChangeEvent evt) {
Property pbProp = mBroker.getProperty(NAMESPACE_ROOT, makePropertyName(evt.getPropertyName()));
PropertyValue[] pbPropValues = new PropertyValue[1];
pbPropValues[0] = PropertyFactory.createPropertyValue(pbProp, evt.getNewValue());
mBroker.changedProperties(pbPropValues, mPartName);
}
In the first line above we get the PropertyBroker Property class corresponding to the property changed from the name of the changed property in the event. In the second line we create the array to store the changed values. In the third line we create the PropertyValue object from the value of the changed property in the event. In the last line we broadcast the changed value to the property broker.
[Error checking has been removed for clarity of discussion. See included source code for full details.]

Helper Class For Actions
Every component view needs to have a registered handler in order to receive notifications of property changes. We introduce the PBHandler.java helper class to help manage this. Since this is constructed by the Expeditor framework, we cannot initialize this specific instance with our data model. In fact, the same handler is used for all instances of a component view. However the event notification includes information about which view is the target of the notification. We retrieve that view, which we require to implement the IDataProvider helper interface. Through this interface we retrieve our data model. We then extract the name of the changed property, the new value, and pass that into a function that sets the value via reflection.
public void runWithEvent(Event event) {
PropertyChangeEvent pEvent = (PropertyChangeEvent)event;
IDataProvider view = (IDataProvider)SWTHelper.locateView(pEvent.getWireDefinition().getTargetEntityId());
setValue(view.getData(), pEvent.getActionDefinition().getName(), pEvent.getPropertyValue().getValue());
}
In the first line above we cast the event to the PropertyChangeEvent specific to this operation. In the second line we extract the wire from the event, the view id from the wire, and use SWTHelper to find the view from the framework. In the third line we call the setValue helper function with the data model (extracted from the view), the name of the changed property (extracted from the action definition in the event) and the new value (extracted from the event).
[Error checking has been removed for clarity of discussion. See included source code for full details.]

Helper Class for the Topology Handler
The TopologyHelper class is given to help set up initial values into your data model. It contains a single static function to do the initialization. This is passed the context of the plugin, the data model, and the id of the view. The function accesses the TopologyHandler, extracts the keys specifically set for this component view (identified by the view ID), finds the values for each of those keys and attempts to set them into the data model.
public static void initialize(BundleContext context, PCSBean bean, String secondaryID) {
ServiceReference ref = context.getServiceReference(TopologyHandler.class.getName( ));
TopologyHandler handler = (TopologyHandler )context.getService(ref);
ComponentData data = handler.getComponentData(secondaryID);
String[] keys = data.getPreferenceKeys();
for (int i = 0; i < keys.length; i++) {
String[] vals = data.getPreference(keys[i]);
PBHandler.setValue(bean, "set"+keys[i], vals[0]);
}
}
The first two lines retrieve the TopologyHandler from the context. The third and fourth line retrieve the settings specific to this view. We then loop through them. Within the loop we extract the value for the iterated key, then we try to set that value into the bean via reflection using the helper function defined in the PBHandler class above.

Putting It All Together
We use all of these helper classes and our data model together in creating our View class. The framework requires this class to extend ViewPart. We need it to implement the IDataProvider interface so that we can get the data model from it later.
public class TagCloudView extends ViewPart implements IDataProvider
When we construct it, after we create the data model, we create an instance of the PBBroadcast class. Since this links itself to the data model, we don't need to keep a reference to it.
public TagCloudView() {
mData = new TagCloudViewBean();
new PBBroadcast(this, mData);
}
In the createPartControl() method (which we must implement for the ViewPart) we do four things.
public void createPartControl(Composite parent) {
mCloud = new TagCloud(parent, SWT.NULL);
mData.addPropertyChangeListener(new PropertyChangeListener(){
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getPropertyName().equals("primaryData")) {
HashMap tags = parseData(mData.getPrimaryData());
mCloud.setPrimaryData(tags);
}
if (evt.getPropertyName().equals("focusedEntity"))
mCloud.setFocusedKey(mData.getFocusedEntity());
}
});
mCloud.addPropertyChangeListener(new PropertyChangeListener(){
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getPropertyName().equals("focused"))
mData.setFocusedEntity(mCloud.getFocusedKey());
}
});
TopologyHelper.initialize(Activator.getDefault().getContext(), mData, getViewSite().getSecondaryId());
}
Firstly in line one we create the actual control and place it in our view.
Second we establish the connection from our data model to the control. We do this by registering a listener for changes on the data model. When it receives one, we pass that onto the control. (Changing the format if required.)
Third we establish the connection from the control to our data model. We do this in the same way, but this time propagating change back when detected.
Lastly we use the help function on the TopologyHelper class to set up the default values into our data model. Since we've already connected it up with the control, these will automatically propagate.
Since each component view needs a unique handler, we need to create a TagCloudHander class. This can subclass the PBHandler helper class. It doesn't need to add any methods.
Now that all the code is in place, we have to let the PropertyHanlder know of it by filling in an extension point. We create an extension under the com.ibm.rcp.propertybroker.ProperBrokerAction id. We fill out the class field to point at our handler. We fill out the file field and point it at our WSDL file.



Integrating Notes Data
There are a number of circumstances where an Eclipse component view can be used to surface Notes data in a different way that using traditional Notes UI elements. This can be done by accessing the Notes Java API. To make things easier, the Java API is packaged into a plugin that is available in the environment. On the Dependencies tab of your plugin editor, just select the "com.ibm.notes.java.api" plugin and all of the APIs will be immediately available.
It is best to keep the access to Notes data asynchronous. When you wish to look up data, use a NotesThread object to execute the lookup. When finished, if the data involves a UI update, remember to spawn off the update into a thread that is run with the UI thread. The code below can be used as a template for this. You might also look at the com.ibm.cademo.sl.comp.leadbrow package included with the Lead Manager example for a fully worked out example.

public class CalculateData implements Runnable
{
public void run()
{
try {
Session ssn = NotesFactory.createSessionWithFullAccess();
...
ssn.recycle();
} catch (Exception e) {
e.printStackTrace();
}
}

public void execute() {
NotesThread nt = new NotesThread(this);
nt.start();
}
}

Going Beyond This
Through this exercise we have introduced several helper functions for creating a component that can be deployed in a composite application. More than that, though, we have created a foundation for creating other components quickly and easily. These functions are not required. Simple components can encompass these steps in their actual code. Or you may find other ways of establishing common usage of the APIs. They are presented here as a helping step to get beyond simple components and onto your first suite of components.
If we wanted to extend an existing component view, we would take the following steps. First we would create the additional fields in our data model, as well as the accessor functions. As part of this we would have to ensure the accessor functions broadcast changes through the Java Bean property change mechanism. Additionally we would add those values to our WSDL file, with unique type definitions if required.
Next we would connect those fields up with the ViewPart to visualize these new values and, if appropriate, allow the user to change those values. No change is necessary to ensure that our new values are broadcast. The PBBroadcast helper class listens for all changes to the Java bean and, as long as the names of the fields are in sync with the names defined in the WSDL file, they will be appropriately broadcast. Similarly, no change is required to accept changes to those values from other component views. Adding their values to the WSDL file will ensure they can be wired up, and the PBHandler class will accept those values, and tie them to the data model through introspection.
If we wished to create another, entirely new, component there are other simple steps. Since the helper functions are not tied to a specific component, we would probably promote them to live in a separate plugin. Each component you produce would then have a dependency on that plugin. [It is recommended that you place only one component view in each plugin. The WSDL files is associated with the components itself. This means that all declared values are valid for all components views in a single plugin. Placing a single component view in each plugin resolves what confusion might result from having properties from several components display when referring to a single one in the CAE UI.]
One would then simply create the new plugin and mark it to depend on the plugin with the helper classes in it. You would create your data model, inheriting from PCSBean, and its coresponding WSDL file. Then the ViewPart which instantiates the data model and implements the IDataProvider interface. In the constructor the PBBroadcast class can be instantiated with a reference to the data model, and in the createPartControl() function a call can be made into the TopologyHelper to set initial values. Lastly a facade Helper class is created as a subclass from PBHandler, and that, with the WSDL file, registered against the com.ibm.rcp.propertybroker.PropertyBrokerAction extension point.
These steps can be repeated for each component you wish to add.


[Sorry, couldn't attach sample project! Available upon request from jo_grant@us.ibm.com.]


Feedback response number JGRT77URM3 created by ~August Umtumitherader on 10/10/2007

Composite Application Articles (~Kirk Frofoovit... 10.Oct.07)
. . Cool! Thanks a lot for that informa... (~Manny Lopvelul... 11.Oct.07)
. . 1.1 Composite Application Sample Ov... (~Kirk Frofoovit... 10.Oct.07)
. . 1.2 Designing Composite Application... (~Kirk Frofoovit... 10.Oct.07)
. . 1.3 Developing Composite Applicatio... (~Kirk Frofoovit... 10.Oct.07)
. . 2.1 Designing Composite Application... (~Kirk Frofoovit... 10.Oct.07)
. . 2.2 Developing Composite Applicatio... (~Kirk Frofoovit... 10.Oct.07)
. . 2.3 Developing Composite Applicatio... (~Kirk Frofoovit... 10.Oct.07)
. . 2.4 Designing Composite Application... (~Kirk Frofoovit... 10.Oct.07)
. . 3.1 Developing Composite Applicatio... (~Kirk Frofoovit... 10.Oct.07)
. . 3.2 Developing Composite Applicatio... (~Kirk Frofoovit... 10.Oct.07)
. . 3.3 Deploying Composite Application... (~Kirk Frofoovit... 10.Oct.07)
. . . . Follow up on two articles. (~Maria Xanjumil... 9.Dec.08)
. . *Thank you very much for doing this... (~Yentl Quetkrot... 10.Oct.07)




Printer-friendly

Search this forum

Member Tools


RSS Feeds

 RSS feedsRSS
All forum posts RSS
All main topics RSS